iT邦幫忙

2018 iT 邦幫忙鐵人賽
DAY 22
2

read me senpai

這篇文章假設你有下列的知識:

  1. 了解實作了 Iterator 界面的物件就可以被 foreach 迭代
  2. 了解 PHP 中的陣列

前言

如果說一般用「return」的函式是只能發射一次,但能重複利用的橡皮筋;
那麼用「yield」的函式就是連弩了吧XD

在這篇文章我會試著從 yield 的原理解釋為什麼它可以達成多次 return 的效果

語法

<?php

// 這個函式會回傳一個 Generator 的物件
function g()
{
    foreach(range(1, 10) as $val) {
    
        // yield 就像函式的中斷點,每次執行到這就會回傳 $val 並中斷函式的執行,
        // 直到下次執行這個函式。
        yield $val;
    }
}

// 這個物件是可以被迭代的,每一次的迭代都會執行到 yield 那行為止。
$generator = g();

// 你應該會好奇:在 g() 裡面只有回傳 $val 而沒有 $key。那 $key 是從何而來的?
// 這是因為 Generator 會為 $val 產生一個對應整數,並當成是它的 $key。
foreach ($generator as $key => $val) {
    echo "{$key}: {$val}" . PHP_EOL;
}

// output:
// 0: 1
// 1: 2
// 2: 3
// 3: 4
// 4: 5
// 5: 6
// 6: 7
// 7: 8
// 8: 9
// 9: 10

你也可以手動指定 $key

<?php

function g()
{
    foreach(range(1, 10) as $val) {
        yield ($val + 1000) => $val;
    }
}

// generator 因為有實作 Iterator 界面,所以可以被用來迭代
foreach (g() as $key => $val) {
    echo "{$key}: {$val}" . PHP_EOL;
}

// output:
// 1001: 1
// 1002: 2
// 1003: 3
// 1004: 4
// 1005: 5
// 1006: 6
// 1007: 7
// 1008: 8
// 1009: 9
// 1010: 10

這語法就像我們在 foreach 那邊使用的一樣,很簡單吧!

注意!

generator 不能用 return 從函式回傳任何值,
只可以用「return;」中斷執行

好處[1]

用 yield 最大的好處就在它相當節省記憶體了!
(而且語法還基本上跟一般用 return 的函式保持一致)

比如如同在 PHP 官網中舉的例子:
如果你使用 range(0, 1000000) 時會吃掉超過 100 MB的記憶體,
但他們用 yield 重製了一個 xrange()
結果居然只吃不到 1 KB 的記憶體!

操作資源時的注意事項[2]

範例程式碼請到這裡

比如說你在 generator(yield function) 中讀取一個檔案,
然後要把它一行一行的印出來...,總之你最後需要執行 fclose() 對吧?

但如果一個 generator 在 yield 之前,
發生了異常被 return; 強制結束

那麼要如何關閉那個檔案呢?

總之加上 finally 就對了

根據 nikita popov 在這裡的說法:

...
在上述的情況下,如果一個 generator 當中有包含 finally 這個區塊,
那麽 finally 區塊會在這些情況發生時被執行。
...

所以你就這樣寫就對了:

<?php

// @link http://php.net/manual/en/language.generators.overview.php#112985
function getLines($file) {
    $f = fopen($file, 'r');
    try {
        while ($line = fgets($f)) {
            yield $line;
        }
    } finally {
        fclose($f);
    }
}

參考

  1. Generators overview
  2. bloodjazman at gmail dot com
  3. rfc:Closing a generator

上一篇
Carbon:setTestNow()輔助測試
下一篇
PHP:升級7.0前的語法檢查
系列文
花式PHP31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言